package com.henry.imkit

import android.annotation.SuppressLint
import com.alibaba.fastjson.JSON
import com.henry.imkit.ErrorCode.WEB_SOCKET_ERROR
import com.henry.imkit.ErrorCode.WEB_SOCKET_FAILED
import com.henry.imkit.ErrorCode.WEB_SOCKET_SUBSCRIBE_FAILED
import com.henry.imkit.models.*
import com.henry.imkit.request.IMRequest
import com.henry.imkit.request.IMRetrofit
import com.henry.imkit.utils.*
import io.reactivex.Completable
import io.reactivex.CompletableTransformer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import io.realm.Realm
import com.henry.imkit.stomp.Stomp
import com.henry.imkit.stomp.StompClient
import com.henry.imkit.stomp.dto.LifecycleEvent
import com.henry.imkit.stomp.dto.StompCommand
import com.henry.imkit.stomp.dto.StompHeader
import com.henry.imkit.stomp.dto.StompMessage
import java.util.*

typealias OnSendSuccessCallback = (msg: IMMessage) -> Unit

object IMManager {

    var token = ""
    var userId = ""


    private var sessionId = ""
    private var mStompClient: StompClient? = null
    private var compositeDisposable: CompositeDisposable? = null
    private var errorFlag = false//Stomp发生错误标识
    private var reconnectionNum = 0//重连次数

    private val sendStompMessages = mutableListOf<StompMessage>()

    private val sendData = mutableMapOf<String, IMMessage>()
    private var onSendSuccess: OnSendSuccessCallback? = null
    private var onSendError: OnErrorCallback? = null

    private var onLoginSuccess: OnSuccessCallback? = null
    private var onLoginError: OnErrorCallback? = null


    private var refreshListener: IMRefreshListener? = null
    private var messageListener: IMMessageListener? = null
    private var messageRevokeListener: IMMessageRevokeListener? = null
    private var conversationInfoListener: IMConversationInfoListener? = null
    private var userStatusListener: IMUserStatusListener? = null
    private var connListener: IMConnListener? = null
    private var eventsListener: IMEventListener? = null

    private var rtcRefreshListener: IMRTCRefreshListener? = null
    private var rtcMessageListener: IMRTCMessageListener? = null

    fun setRefreshListener(listener: IMRefreshListener?) {
        refreshListener = listener
    }

    fun setMessageListener(listener: IMMessageListener?) {
        messageListener = listener
    }

    fun setMessageRevokeListener(listener: IMMessageRevokeListener?) {
        messageRevokeListener = listener
    }

    fun setConversationInfoListener(listener: IMConversationInfoListener?) {
        conversationInfoListener = listener
    }

    fun setUserStatusListener(listener: IMUserStatusListener?) {
        userStatusListener = listener
    }

    fun setConnListener(listener: IMConnListener?) {
        connListener = listener
    }

    fun setEventListener(listener: IMEventListener?) {
        eventsListener = listener
    }

    fun setRTCRefreshListener(listener: IMRTCRefreshListener?) {
        rtcRefreshListener = listener
    }

    fun setRTCMessageListener(listener: IMRTCMessageListener?) {
        rtcMessageListener = listener
    }

    fun login(token: String, userId: String, onSuccess: OnSuccessCallback? = null, onError: OnErrorCallback? = null) {
        this.token = token
        this.userId = userId
        onLoginSuccess = onSuccess
        onLoginError = onError

        Realm.init(IMKit.instance.context)

        openWebSocket()
    }

    fun openWebSocket() {

        IMRequest.imSessionId({
            sessionId = it
            IMRequest.websocket({
                initWebsocket()
            }, onLoginError)
        }, onLoginError)

    }

    private fun getUrl(): String {
        var wsDomain = IMKit.instance.domain.replace("https", "wss")
        wsDomain = wsDomain.replace("http", "ws")

        val random = Random().nextInt(999)

        val url = "$wsDomain/v2/im/websocket/$random/$sessionId/websocket?access_token=$token"

        IMLog("WebSocket url: $url")

        return url
    }

    /**
     * 初始化Stomp，并自动连接
     */
    private fun initWebsocket() {
        mStompClient = Stomp.over(
            Stomp.ConnectionProvider.OKHTTP,
            getUrl(),
            null,
            IMRetrofit.getOkHttpClient()
        )
        resetSubscriptions()
        connectStomp()
    }

    /**
     * 连接Stomp
     */
    private fun connectStomp() {

//        mStompClient.withClientHeartbeat(1000).withServerHeartbeat(1000)

        resetSubscriptions()

        //监听lifecycleEvent的回调状态
        val dispLifecycle = mStompClient!!.lifecycle()
            .doOnError { throwable: Throwable -> IMLogErr("连接异常：$throwable.message") }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                when (it.type) {
                    LifecycleEvent.Type.OPENED -> {
                        IMLog("Stomp connection opened ")

                        subscribeChat()

                        onLoginSuccess?.invoke()

                    }
                    LifecycleEvent.Type.ERROR -> {
                        errorFlag = true
                        IMLog("Stomp connection error " + it.exception)
                        onLoginError?.invoke(WEB_SOCKET_ERROR, it.exception.message.toString())
                    }
                    LifecycleEvent.Type.CLOSED -> {
                        IMLogErr("Stomp connection closed " + it.exception)
                        resetSubscriptions()
                        reconnectionNum++
                        if (errorFlag && reconnectionNum < 3) {
                            IMLog("连接异常断开，第${reconnectionNum}次自动重连")
                            connectStomp()
                        }
                    }
                    else -> {
                        IMLogErr("Stomp connection other " + it.exception)
                        onLoginError?.invoke(WEB_SOCKET_FAILED, it.exception.message.toString())
                    }
                }

            }, {
                IMLogErr("stomp连接时subscribe发生异常：${it?.message}")
                onLoginError?.invoke(WEB_SOCKET_SUBSCRIBE_FAILED, it.toString())

            })

        compositeDisposable?.add(dispLifecycle)

        mStompClient?.connect()

    }

    @SuppressLint("CheckResult")
    fun subscribeChat() {

        val dispTopic1 = mStompClient!!.topic("/v2/im/message")
            .doOnError { throwable: Throwable -> IMLogErr("订阅异常1：$throwable.message") }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                val payloadStr = it.payload
                IMLog("订阅消息1： $payloadStr")
            }, {
                IMLogErr("stomp订阅时subscribe发生异常1：${it?.message}")
            })
        compositeDisposable?.add(dispTopic1)

        val dispTopic2 = mStompClient!!.topic("/im/message")
            .doOnError { throwable: Throwable -> IMLogErr("订阅异常2：$throwable.message") }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                val payloadStr = it.payload
                IMLog("订阅消息2： $payloadStr")
            }, {
                IMLogErr("stomp订阅时subscribe发生异常2：${it?.message}")
            })
        compositeDisposable?.add(dispTopic2)
//
        val dispTopic3 = mStompClient!!.topic("/user/im/message")
            .doOnError { throwable: Throwable -> IMLogErr("订阅异常3：$throwable.message") }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                val payloadStr = it.payload
                it.findHeader("taskid")
                IMLog("接收到消息： $payloadStr")

                handleData(payloadStr)

            }) {
                IMLogErr("stomp订阅时subscribe发生异常3：${it}")
            }
        compositeDisposable?.add(dispTopic3)

    }


    private fun resetSubscriptions() {
        compositeDisposable?.dispose()
        compositeDisposable = CompositeDisposable()
    }

    fun disconnectStomp() {
        mStompClient?.disconnect()
    }

    fun onDestroy() {
        mStompClient?.disconnect()
        compositeDisposable?.dispose()
        onSendSuccess = null
        onSendError = null
        sendData.clear()
        sendStompMessages.clear()
    }

    private fun handleData(data: String) {
        val chat = JSON.parseObject(data, IMChatData::class.java)

        if (handleAcceptFailData(chat)) {
            return
        }
        handleChat(chat.chatNotices)
        handleRTC(chat.chatRTCNotices)

    }

    private fun handleAcceptFailData(data: IMChatData): Boolean {
        // TODO 发送失败 taskid
        var msg: StompMessage? = null
        if (data.taskid != null) {
            for (sendMsg in sendStompMessages) {
                val taskId = sendMsg.findHeader("taskid")
                if (taskId != null && data.taskid == taskId) {
                    sendStompMessages.remove(sendMsg)
                    msg = sendMsg
                    break
                }
            }
        }
        if (data.informationcode != null && data.informationcode!! > 0) {
            if (msg != null) {
                onSendError?.invoke(data.informationcode!!, data.msg.toString())
            }
            return true
        }
        return false
    }

    private fun handleChat(notices: List<IMChatNotice>? = null) {
        if (notices == null) {
            return
        }

        val convArray = mutableListOf<IMChatNotice>()
        val msgArray = mutableListOf<MessageData>()

        for (item in notices) {
            // 会话
            convArray.add(item)

            // newMessages
            if (item.newMessages != null) {
                for (msg in item.newMessages!!) {
                    msgArray.add(msg)
                }
            }

            // newEvents
            if (item.newEvents != null) {
                for (event in item.newEvents!!) {
                    eventsListener?.onNewEvent(event)

                    RealmHelper.handleEvent(event) {
                        refreshListener?.onRefreshConversations()
                    }
                }
            }

        }

        if (convArray.size > 0) {
            RealmHelper.handleConversation(convArray) {
                refreshListener?.onRefreshConversations()
            }

            for (conv in convArray) {
                if (conv.chatNoticeType == IM_NOTICE_TYPE_MODIFY_GROUP) {
                    // 更改了群资料
                    conversationInfoListener?.onChangeConversation(conv.eacgid.toString())
                }
            }
        }

        // 消息 (包含 发送消息成功回调)
        // 对比下发送的消息（eamrandom），有则为发送消息，无为新消息

        val newMsgArray = mutableListOf<IMMessage>()

        if (msgArray.size > 0) {

            for (item in msgArray) {
                // 发送的消息
                if (item.eamrandom != null && sendData.containsKey(item.eamrandom)) {
                    val msg = IMConvertUtils.convertMessage(item)
                    onSendSuccess?.invoke(msg)
                    sendData.remove(item.eamrandom)
                } else {
                    val msg = IMConvertUtils.convertMessage(item)
                    if (msg.eamessagetype == IM_ELEM_REVOKE) {
                        // 撤回消息
                        messageRevokeListener?.onRevokeMessage(msg)
                    } else {
                        newMsgArray.add(msg)
                    }
                }
            }

        }

        if (newMsgArray.size > 0) {
            messageListener?.onNewMessage(newMsgArray)
        }

    }

    private fun handleRTC(notices: List<IMChatNotice>? = null) {
        if (notices == null) {
            return
        }

        val msgArray = mutableListOf<IMMessage>()

        for (item in notices) {
            val conversationData = IMConvertUtils.convertConversationData(item)
            if (item.chatNoticeType == IM_NOTICE_TYPE_NEW_GROUP) {
                rtcRefreshListener?.onInvite(conversationData)
            } else if (item.chatNoticeType == IM_NOTICE_TYPE_EXIT_GROUP) {
                rtcRefreshListener?.onExit(conversationData)
            }

            // newMessages
            if (item.newMessages != null) {
                for (msg in item.newMessages!!) {
                    msgArray.add(IMConvertUtils.convertMessage(msg))
                }
            }

        }

        if (msgArray.size > 0) {
            rtcMessageListener?.onNewMessage(msgArray)

        }

    }

    fun sendMessage(data: Any?, dgid: String?,
                    success: OnSendSuccessCallback? = null,
                    error: OnErrorCallback? = null) {
        if (dgid == null || dgid.isEmpty() || data == null) {
            return
        }

        val msg = IMSendMessage()
        msg.eamessagecontent = listOf(data)

        if (msg.eamrandom != null) {
            sendData[msg.eamrandom!!] = IMConvertUtils.convertIMMessage(msg)
        }

        onSendSuccess = success
        onSendError = error

        val jsonString = JSON.toJSONString(msg)

        IMLog("sendMessage jsonString: $jsonString")

        val str = Base64Utils.getBase64(jsonString)

        val destination = "/v2/im/to/$dgid/sendForBase64"
        sendTo(destination, str)
    }

    fun sendMessage(msg: IMMessage?, dgid: String?,
                    success: OnSendSuccessCallback? = null,
                    error: OnErrorCallback? = null) {
        if (dgid == null || dgid.isEmpty() || msg == null) {
            return
        }

        if (msg.eamrandom != null) {
            sendData[msg.eamrandom!!] = msg
        }

        onSendSuccess = success
        onSendError = error

        val jsonString = JSON.toJSONString(msg)

        IMLog("sendMessage jsonString: $jsonString")

        val str = Base64Utils.getBase64(jsonString)

        val destination = "/v2/im/to/$dgid/sendForBase64"
        sendTo(destination, str)

    }

    fun revokeMessage(dgid: String?, mid: String) {
        if (dgid == null || dgid.isEmpty()) {
            return
        }
        val destination = "/v2/im/to/$dgid/$mid/revoke"
        sendTo(destination, null)
    }

    fun readMessage(dgid: String?) {
        if (dgid == null || dgid.isEmpty()) {
            return
        }
        val destination = "/v2/im/to/$dgid/read"
        sendTo(destination, null)
    }

    fun readVoiceMessage(dgid: String?, mid: String) {
        if (dgid == null || dgid.isEmpty()) {
            return
        }
        val destination = "/v2/im/to/$dgid/$mid/read"
        sendTo(destination, null)
    }

    // 用于视频通话 进入聊天
    fun sendSubscribe(conversationId: String) {
        val str = "/v2/im/to/$conversationId/subscribe"
        sendTo(str, null)
    }

    // 退出聊天
    fun sendUnsubscribe(conversationId: String) {
        val str = "/v2/im/to/$conversationId/unsubscribe"
        sendTo(str, null)
    }

    private fun sendTo(destination: String, body: String?) {
        val headers: MutableList<StompHeader> = ArrayList()
        headers.add(StompHeader(StompHeader.DESTINATION, destination))
        headers.add(StompHeader("taskid", UUID.randomUUID().toString()))
        val msg = StompMessage(StompCommand.SEND, headers, body)
        sendStompMessages.add(msg)
        compositeDisposable?.add(mStompClient!!.send(msg).compose(applySchedulers()).subscribe())
    }

    private fun applySchedulers(): CompletableTransformer {
        return CompletableTransformer { upstream: Completable ->
            upstream
                    .unsubscribeOn(Schedulers.newThread())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
        }
    }


    fun getConversations(
            onSuccess: ((list: List<Conversation>) -> Unit)? = null,
            onError: OnErrorCallback? = null
    ) {
        IMRequest.getConversations("", 1, 10000, { list: List<ConversationData>, i: Int ->

            val arr = IMConvertUtils.convertConversations(list)

            RealmHelper.save(arr)

            onSuccess?.invoke(arr)

        }, onError)
    }

    fun getLocalConversations(
            onSuccess: ((list: List<Conversation>) -> Unit)? = null,
    ) {
        val arr = RealmHelper.findAll()
        onSuccess?.invoke(arr)
    }

    fun getMessages(
            conversationId: String, time: String, count: Int,
            onSuccess: ((data: List<IMMessage>) -> Unit)? = null,
            onError: OnErrorCallback? = null
    ) {
        IMRequest.messages(conversationId, time, count, {
            val list = IMConvertUtils.convertMessages(it)
            onSuccess?.invoke(list)
        }, onError)
    }

    fun addMembers(
            conversationId: String, userId: String,
            onSuccess: (() -> Unit)? = null,
            onError: OnErrorCallback? = null
    ) {
        IMRequest.addMembers(conversationId, userId, {
            onSuccess?.invoke()
        }, onError)
    }

    fun deleteMembers(
            conversationId: String, userId: String,
            onSuccess: (() -> Unit)? = null,
            onError: OnErrorCallback? = null
    ) {
        IMRequest.deleteMembers(conversationId, userId, {
            onSuccess?.invoke()
        }, onError)
    }

    fun createConversation(
        name: String, type: String, users: String, dgid: String, sessionId: String,
        onSuccess: ((data: ConversationData) -> Unit)? = null,
        onError: OnErrorCallback? = null
    ) {
        IMRequest.createConversation(name, type, users, dgid, sessionId, onSuccess, onError)
    }

    fun getConversationInfo(
        conversationId: String,
        onSuccess: ((info: ConversationDetail?, members: List<Member>?) -> Unit)? = null,
        onError: OnErrorCallback? = null
    ) {
        IMRequest.getConversationInfo(conversationId, {
            onSuccess?.invoke(it.groupInfo, it.memberList)
        }, onError)
    }

    fun updateConversationName(
            conversationId: String, name: String,
            onSuccess: (() -> Unit)? = null,
            onError: OnErrorCallback? = null
    ) {
        IMRequest.updateConversation(conversationId, name, {
            onSuccess?.invoke()
        }, onError)
    }

    fun getConversationUsers(
        conversationId: String, keyword: String, page: Int, size: Int,
        onSuccess: ((data: List<User>) -> Unit)? = null,
        onError: OnErrorCallback? = null
    ) {
        IMRequest.getUserList(conversationId, keyword, page, size, {
            onSuccess?.invoke(it)
        }, onError)
    }

}